package com.hwtx.form.domain.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.google.common.collect.Sets;
import com.hwtx.form.domain.listener.DataPermValueChangeEvent;
import com.hwtx.form.domain.listener.EventPublisher;
import com.hwtx.form.domain.repo.*;
import com.hwtx.form.dto.RoleDataResourceDto;
import com.hwtx.form.persistence.entity.SysFunctionResourceMapping;
import com.hwtx.form.persistence.entity.SysRoleDataPermPro;
import com.hwtx.form.persistence.mapper.SysFunctionResourceMappingMapper;
import com.hwtx.form.persistence.mapper.SysRoleDataPermProMapper;
import io.geekidea.boot.auth.cache.LoginCache;
import io.geekidea.boot.common.constant.SystemConstant;
import io.geekidea.boot.framework.exception.BusinessException;
import io.geekidea.boot.framework.page.Paging;
import io.geekidea.boot.system.dto.RoleMenusDto;
import io.geekidea.boot.system.dto.UserDto;
import io.geekidea.boot.system.entity.*;
import io.geekidea.boot.system.enums.SysMenuType;
import io.geekidea.boot.system.query.SysMenuQuery;
import io.geekidea.boot.system.query.SysUserQuery;
import io.geekidea.boot.system.vo.SysMenuTreeVo;
import io.geekidea.boot.system.vo.SysUserVo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class RacServiceImpl implements RacService {
    @Resource
    SysRoleService sysRoleService;
    @Resource
    RoleDataResourceRepo roleDataResourceRepo;
    @Resource
    EventPublisher eventPublisher;
    @Resource
    private SysUserRepo sysUserRepo;
    @Resource
    private RoleUserRepo roleUserRepo;
    @Resource
    private OrgUserRepo orgUserRepo;
    @Resource
    private OrgRoleRepo orgRoleRepo;
    @Resource
    private RoleResRepo roleResRepo;
    @Resource
    private ResourceService resourceService;
    @Resource
    private SysRoleDataPermProMapper sysRoleDataPermProMapper;
    @Resource
    private SysFunctionResourceMappingMapper sysFunctionResourceMappingMapper;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveUserInfo(UserDto userDto) {
        if (userDto.getId() == null || userDto.getId() == -1) {
            userDto.setId(null);
            return saveUser(userDto);
        } else {
            return updateUser(userDto);
        }
    }

    @Override
    public Set<Long> getUserResources(Long userId) {
        return resourceService.getUserResources(userId, null);
    }

    @Override
    public RoleDataResourceDto getRoleDataResource(Long roleId) {
        RoleDataResourceDto roleDataResource = new RoleDataResourceDto();
        if (roleId == null) {
            return roleDataResource;
        }
        SysRole sysRole = sysRoleService.getById(roleId);
        if (sysRole == null) {
            throw new BusinessException("角色不存在，roleId = " + roleId);
        }
        roleDataResource.setRoleId(roleId);
        roleDataResource.setRoleCode(sysRole.getCode());
        roleDataResource.setRoleName(sysRole.getName());
        RoleDataResource roleData = roleDataResourceRepo.getOne(new LambdaQueryWrapper<RoleDataResource>().eq(RoleDataResource::getRoleId, roleId));
        if (roleData != null) {
            roleDataResource.setId(roleData.getId());
            roleDataResource.setDataType(roleData.getDataType());
            roleDataResource.setOrgIds(roleData.getOrgIds());
        }
        roleDataResource.setRows(getRoleForMenus(roleId, null));
        return roleDataResource;
    }

    @Override
    public List<SysMenuTreeVo> getRoleForMenus(Long roleId, String keyword) {
        List<Long> resourceIds = resourceService.getMenuIdsByRoleId(roleId, List.of(SysMenuType.DIR.getCode(), SysMenuType.MENU.getCode(), SysMenuType.FUN_PERMISSION.getCode()));
        if (resourceIds.isEmpty()) {
            return List.of();
        }
        List<Long> hasPermResIds = sysRoleDataPermProMapper.selectList(new LambdaQueryWrapper<SysRoleDataPermPro>().in(SysRoleDataPermPro::getMenuId, resourceIds))
                .stream().map(SysRoleDataPermPro::getMenuId).toList();
        SysMenuQuery query = new SysMenuQuery();
        query.setResourceIds(resourceIds);
        query.setKeyword(keyword);
        query.setHasPermResIds(hasPermResIds);
        query.setResourceTreeBuildCallback(vo -> {
            if (query.getHasPermResIds() != null) {
                vo.setHasDataPerm(query.getHasPermResIds().contains(vo.getId()));
            }
            if (Objects.equals(vo.getType(), SysMenuType.FUN_PERMISSION.getCode())) {
                return sysFunctionResourceMappingMapper.selectList(new LambdaQueryWrapper<SysFunctionResourceMapping>().eq(SysFunctionResourceMapping::getResourceId, vo.getId()))
                        .stream().anyMatch(mapping -> resourceService.containListUrl(mapping.getUrl()));
            }
            return true;
        });
        return resourceService.getResourceTree(query);
    }

    @Override
    @Deprecated
    public List<RoleDataResourceDto> getRoleDataResourceByUser(Long userId) {
        Set<Long> userRoles = Sets.newHashSet(sysUserRepo.getRolesByUserId(userId));
        List<Long> orgIds = sysUserRepo.getOrgIdsByUserId(userId);
        if (CollectionUtils.isNotEmpty(orgIds)) {
            List<SysOrgRole> orgRoles = orgRoleRepo.list(new LambdaQueryWrapper<SysOrgRole>().in(SysOrgRole::getOrgId, orgIds));
            Set<Long> orgRoleIds = orgRoles.stream().map(SysOrgRole::getRoleId).collect(Collectors.toSet());
            userRoles.addAll(orgRoleIds);
        }
        return userRoles.stream().map(this::getRoleDataResource).collect(Collectors.toList());
    }

    @Override
    public boolean saveRoleDataResource(RoleDataResourceDto roleDataResourceDto) {
        RoleDataResource resource = new RoleDataResource();
        boolean ret;
        if (roleDataResourceDto.getId() != null) {
            resource.setId(roleDataResourceDto.getId());
            resource.setDataType(roleDataResourceDto.getDataType());
            resource.setOrgIds(roleDataResourceDto.getOrgIds());
            if (!Objects.equals(roleDataResourceDto.getDataType(), SystemConstant.DATA_TYPE_SPECIFY_ORG)) {
                resource.setOrgIds("");
            }
            resource.setUpdateTime(new Date());
            resource.setUpdateId(LoginCache.get().getUsername());
            ret = roleDataResourceRepo.updateById(resource);
        } else {
            resource.setDataType(roleDataResourceDto.getDataType());
            resource.setOrgIds(roleDataResourceDto.getOrgIds());
            resource.setRoleId(roleDataResourceDto.getRoleId());
            ret = roleDataResourceRepo.save(resource);
        }
        eventPublisher.publishEvent(new DataPermValueChangeEvent(this, resource.getId()));
        return ret;
    }

    @Override
    public List<Long> getOrgIdsByUserId(Long userId) {
        return sysUserRepo.getOrgIdsByUserId(userId);
    }

    @Override
    public Paging<SysUserVo> getAssignedOrgUsersPage(SysUserQuery query) {
        return sysUserRepo.getAssignedOrgUsersPage(query);
    }

    @Override
    public List<Long> getRolesByUserId(Long userId) {
        return sysUserRepo.getRolesByUserId(userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean setRoleMenus(RoleMenusDto roleMenusDto) {
        Long roleId = roleMenusDto.getRoleId();
        List<Long> menuIds = roleMenusDto.getMenuIds();
        SysRole sysRole = sysRoleService.getById(roleId);
        if (sysRole == null) {
            throw new BusinessException("角色不存在");
        }
        // 先删除角色权限关系
        roleResRepo.deleteSysRoleMenuByRoleId(roleId);
        List<SysRoleMenu> sysRoleMenus = new ArrayList<>();
        Set<Long> completionMenuIds = completionMenuParentIds(menuIds);
        completionMenuIds.addAll(menuIds);
        for (Long completionMenuId : completionMenuIds) {
            SysRoleMenu sysRoleMenu = new SysRoleMenu();
            sysRoleMenu.setRoleId(roleId);
            sysRoleMenu.setMenuId(completionMenuId);
            sysRoleMenus.add(sysRoleMenu);
        }
        boolean flag = roleResRepo.saveBatch(sysRoleMenus);
        if (!flag) {
            throw new BusinessException("设置角色权限异常");
        }
        return true;
    }

    /**
     * 补全菜单父ID
     */
    private Set<Long> completionMenuParentIds(List<Long> choiceMenuIds) {

        Map<Long, Long> idMapPId = resourceService.getResourceWithPId();
        Set<Long> resourceIds = new HashSet<>();
        choiceMenuIds.forEach(menuId -> recursionCompletionMenu(idMapPId, menuId, resourceIds));
        return resourceIds;
    }

    /**
     * 递归补全菜单ID集合
     */
    private void recursionCompletionMenu(Map<Long, Long> idMapPId, Long resourceId, Set<Long> resourceIds) {
        Long parentId = idMapPId.get(resourceId);
        // 判断是否存在此父ID，不存在则添加
        if (!resourceIds.contains(parentId)) {
            if (!Objects.equals(parentId, SystemConstant.ROOT_MENU_ID)) {
                resourceIds.add(parentId);
                recursionCompletionMenu(idMapPId, parentId, resourceIds);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteUser(Long userId) {
        sysUserRepo.removeById(userId);
        orgUserRepo.remove(new LambdaQueryWrapper<SysOrgUser>().eq(SysOrgUser::getUserId, userId));
        roleUserRepo.remove(new LambdaQueryWrapper<SysRoleUser>().eq(SysRoleUser::getUserId, userId));
        return true;
    }

    @Override
    public void bindOrgUsers(List<SysOrgUser> orgUsers) {
        orgUsers.stream().filter(orgUser -> orgUserRepo.count() == 0).forEach(orgUser -> orgUserRepo.save(orgUser));
    }

    @Override
    public boolean unBindOrgUsers(List<SysOrgUser> orgUsers) {
        orgUsers.forEach(orgUser -> orgUserRepo.remove(new LambdaQueryWrapper<SysOrgUser>()
                .eq(SysOrgUser::getUserId, orgUser.getUserId())
                .eq(SysOrgUser::getOrgId, orgUser.getOrgId())));
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean assignRoles(List<SysOrgRole> orgRoles) {
        orgRoles.forEach(orgRole -> orgRoleRepo.remove(new LambdaQueryWrapper<SysOrgRole>()
                .eq(SysOrgRole::getOrgId, orgRole.getOrgId())));
        orgRoles.forEach(orgRole -> orgRoleRepo.save(orgRole));
        return true;
    }

    @Override
    public List<SysOrgRole> getRolesByOrgId(Long orgId) {
        return orgRoleRepo.list(new LambdaQueryWrapper<SysOrgRole>()
                .eq(SysOrgRole::getOrgId, orgId));
    }

    private boolean saveUser(UserDto userDto) {
        Long userId = sysUserRepo.addSysUser(userDto);
        roleUserRepo.saveBatch(userDto.getRoleIds().stream().map(roleId -> {
            SysRoleUser sysRoleUser = new SysRoleUser();
            sysRoleUser.setRoleId(roleId);
            sysRoleUser.setUserId(userId);
            return sysRoleUser;
        }).collect(Collectors.toList()));
        if (CollectionUtils.isNotEmpty(userDto.getOrgIds())) {
            orgUserRepo.saveBatch(userDto.getOrgIds().stream().map(orgId -> {
                SysOrgUser orgUser = new SysOrgUser();
                orgUser.setOrgId(orgId);
                orgUser.setUserId(userId);
                return orgUser;
            }).collect(Collectors.toList()));
        }
        return true;
    }

    private boolean updateUser(UserDto userDto) {
        Long userId = sysUserRepo.updateSysUser(userDto);
        LambdaUpdateWrapper<SysRoleUser> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(SysRoleUser::getUserId, userId);
        roleUserRepo.remove(wrapper);
        roleUserRepo.saveBatch(userDto.getRoleIds().stream().map(roleId -> {
            SysRoleUser sysRoleUser = new SysRoleUser();
            sysRoleUser.setRoleId(roleId);
            sysRoleUser.setUserId(userId);
            return sysRoleUser;
        }).collect(Collectors.toList()));

        LambdaUpdateWrapper<SysOrgUser> orgUserWrapper = new LambdaUpdateWrapper<>();
        orgUserWrapper.eq(SysOrgUser::getUserId, userId);
        orgUserRepo.remove(orgUserWrapper);
        if (CollectionUtils.isNotEmpty(userDto.getOrgIds())) {
            return orgUserRepo.saveBatch(userDto.getOrgIds().stream().map(orgId -> {
                SysOrgUser orgUser = new SysOrgUser();
                orgUser.setOrgId(orgId);
                orgUser.setUserId(userId);
                return orgUser;
            }).collect(Collectors.toList()));
        }
        return true;
    }

    @Override
    public List<Long> getAllRoleIdsByUserId(Long userId) {
        List<Long> roleIds = sysUserRepo.getRolesByUserId(userId);
        roleIds.addAll(orgUserRepo.getOrgRoleIdsByUserId(userId));
        return roleIds;
    }
}
